﻿/*
 * @author: wizardc
 */

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace DouEditor.Extend
{
    public sealed class MixedEditorPlayMgr
    {
        private static MixedEditorPlayMgr _instance;
        public static MixedEditorPlayMgr instance => _instance ??= new MixedEditorPlayMgr();

        private Dictionary<MixedEditorPlayData, MixedEditorPlayInfo> _map = new Dictionary<MixedEditorPlayData, MixedEditorPlayInfo>();
        private double _prevTime;

        private MixedEditorPlayMgr()
        {
            _prevTime = EditorApplication.timeSinceStartup;

            EditorApplication.update -= OnEditorUpdate;
            EditorApplication.update += OnEditorUpdate;
        }

        public MixedEditorPlayInfo AddData(MixedEditorPlayData data)
        {
            RemoveData(data);
            var info = new MixedEditorPlayInfo(data);
            _map.Add(data, info);
            return info;
        }

        public MixedEditorPlayInfo Get(MixedEditorPlayData data)
        {
            if (_map.ContainsKey(data))
            {
                return _map[data];
            }
            return null;
        }

        public void RemoveData(MixedEditorPlayData data)
        {
            if (_map.ContainsKey(data))
            {
                var info = _map[data];
                info.Destroy();
                _map.Remove(data);
            }
        }

        private void OnEditorUpdate()
        {
            var now = EditorApplication.timeSinceStartup;
            var passedTime = now - _prevTime;
            _prevTime = now;
            foreach (var kv in _map)
            {
                kv.Value.Update((float) passedTime);
            }
        }
    }

    public class MixedEditorPlayData
    {
        public static MixedEditorPlayData Collect(GameObject go)
        {
            var data = new MixedEditorPlayData();
            var animatorArr = go.GetComponentsInChildren<Animator>(false);
            foreach (var animator in animatorArr)
            {
                var animationData = AnimationEditorData.Collect(animator);
                var playInfo = AnimatorEditorPlayMgr.instance.AddData(animationData);
                playInfo.isLoop = false;
                playInfo.timeGoesOn = true;
                data.animatorList.Add(playInfo);
            }
            var particleSystemArr = go.GetComponentsInChildren<ParticleSystem>(false);
            foreach (var particleSystem in particleSystemArr)
            {
                var playInfo = ParticleEditorPlayMgr.instance.AddData(particleSystem);
                playInfo.withChildren = false;
                playInfo.isLoop = false;
                playInfo.timeGoesOn = true;
                data.particleList.Add(playInfo);
            }
            return data;
        }

        public List<AnimatorEditorPlayInfo> animatorList = new List<AnimatorEditorPlayInfo>();
        public List<ParticleEditorPlayInfo> particleList = new List<ParticleEditorPlayInfo>();
    }

    public class MixedEditorPlayInfo
    {
        private MixedEditorPlayData _data;

        private bool _playing;
        private float _currentTime;
        private float _totalTime;
        private bool _isLoop = false;
        private bool _timeGoesOn = false;
        private float _ratio = 1;

        public MixedEditorPlayData data => _data;

        public bool playing => _playing;

        public float currentTime
        {
            set
            {
                _currentTime = value;
                Foreach(anim =>
                {
                    anim.currentTime = _currentTime;
                }, part =>
                {
                    part.currentTime = _currentTime;
                });
            }
            get => _currentTime;
        }

        public float totalTime => _totalTime;

        public bool isLoop
        {
            set => _isLoop = value;
            get => _isLoop;
        }

        /// <summary>
        /// 非循环模式下，时间到达后是否继续计时
        /// </summary>
        public bool timeGoesOn
        {
            set => _timeGoesOn = value;
            get => _timeGoesOn;
        }

        public float ratio
        {
            set
            {
                _ratio = value;
                Foreach(anim =>
                {
                    anim.ratio = _ratio;
                }, part =>
                {
                    part.ratio = _ratio;
                });
            }
            get => _ratio;
        }

        public MixedEditorPlayInfo(MixedEditorPlayData data)
        {
            _data = data;
            Foreach(anim =>
                {
                    _totalTime = Mathf.Max(_totalTime, (float) anim.totalTime);
                },
                part =>
                {
                    _totalTime = Mathf.Max(_totalTime, part.totalTime);
                });
        }

        private void Foreach(Action<AnimatorEditorPlayInfo> onAnimator, Action<ParticleEditorPlayInfo> onParticle)
        {
            foreach (var viewer in _data.animatorList)
            {
                onAnimator(viewer);
            }
            foreach (var viewer in _data.particleList)
            {
                onParticle(viewer);
            }
        }

        public void Update(float passTime)
        {
            if (_playing)
            {
                _currentTime += passTime * _ratio;
                if (_currentTime >= _totalTime)
                {
                    if (_isLoop)
                    {
                        _currentTime = _currentTime % _totalTime;
                        Play();
                    }
                    else if (!_timeGoesOn)
                    {
                        _currentTime = _totalTime;
                    }
                }
            }
            Foreach(anim =>
            {
                anim.Update(passTime);
            }, part =>
            {
                part.Update(passTime);
            });
        }

        public void Play()
        {
            Stop();
            _playing = true;
            _currentTime = 0;
            Foreach(anim =>
            {
                anim.Play();
            }, part =>
            {
                part.Play();
            });
        }

        public void Pause()
        {
            _playing = false;
            Foreach(anim =>
            {
                anim.Pause();
            }, part =>
            {
                part.Pause();
            });
        }

        public void Resume()
        {
            _playing = true;
            Foreach(anim =>
            {
                anim.Resume();
            }, part =>
            {
                part.Resume();
            });
        }

        public void Stop()
        {
            _playing = false;
            _currentTime = 0;
            Foreach(anim =>
            {
                anim.Stop();
            }, part =>
            {
                part.Stop();
            });
        }

        public void Destroy()
        {
            Foreach(anim =>
                {
                    AnimatorEditorPlayMgr.instance.RemoveData(anim.data);
                },
                part =>
                {
                    ParticleEditorPlayMgr.instance.RemoveData(part.particleSystem);
                });
        }
    }
}
